"""
Author: runyu.ma

backend.py
"""

import sys
from .core.dslc_parser import ASTNode, UnaryOp, BinaryOp, BlockOp, Identifier, Number, String
from .core.dslc_symbol_table import SymbolTable
from .corefunc import import_pyfile

__CCODE_TEMP__ = '''
'''

#TODO: 经过pass中处理meta, c的替换在evaluation中执行，在backend这里执行pystr，完成程序的功能。eval_pystr是冗余设计。
#NOTE: 要不要把python的exec也放到evaluation中执行？保证循环的正确性
#NOTE: 另一种方案是把循环变量也做新的定义，i=1..10 -> i1=1,i2=2...，依旧在这里处理c的替换

EXEC_NS_G = {}
EXEC_NS_L = {}

CCODE_GEN_DICT = {}

def ccode_gen(stab: SymbolTable, ast: ASTNode):
    '''生成c代码'''
    global EXEC_NS_G, EXEC_NS_L
    global CCODE_GEN_DICT

    EXEC_NS_G = globals()
    EXEC_NS_L = locals()
    if not isinstance(ast, BlockOp):
        raise ValueError("ccode gen should work at the largest BlockOp")
    #c code只做static，evaluation之前要把所有的loop和if平铺
    __ccode_gen_static(stab, ast)
    #
    ccode_res = {}
    for k in CCODE_GEN_DICT:
        res = CCODE_GEN_DICT[k]
        ccode_res[k] = res
    return ccode_res


def __inst_str(ast: ASTNode) -> str:
    if isinstance(ast, Identifier):
        return ast.name
    if isinstance(ast, Number):
        return ast.value
    if isinstance(ast, String):
        return f'\"{ast.value}\"'
    raise ValueError("Node is expr")


def __ccode_gen_static(stab: SymbolTable, ast: BlockOp, pre_env=None, pre_param=None):
    #retstr = str(ast) + '\n'
    global CCODE_GEN_DICT
    env = []
    if pre_env:
        env.extend(pre_env)
    opslen = len(ast.oplist)
    opidx = -1
    sdeepth = 0
    while opidx < opslen-1:
        opidx += 1
        op = ast.oplist[opidx]
        if isinstance(op, UnaryOp) and op.op == ':':
            if isinstance(op.x, Identifier):
                env.append(op.x.name)
                #sdeepth += 1
                #stab.enter_scope(op.x.name)
            elif isinstance(op.x, BinaryOp):
                env.append(op.x.l.name+"::"+op.x.r.value)
                #sdeepth += 1
                #stab.enter_scope(op.x.l.name+"::"+op.x.r.value)
            else:
                raise ValueError('can not parse COLON UnaryOp')
        else:
            break

    for e in env:
        sdeepth += 1
        stab.enter_scope(e)

    param = stab.lsvisiable()

    for _ in range(sdeepth):
            stab.exit_scope()
    if pre_param:
        param.update(pre_param)
        #env.extend(pre_env)
    #处理现在env
    cfilename = None
    cfunchead = None
    is_def_sec = False
    is_fun_sec = False
    for ftr in env:
        if ftr.startswith('cfile::'):
            cfilename = ftr[7:]
        if ftr.startswith('cdef'):
            is_def_sec = True
        if ftr.startswith('cfun::'):
            is_fun_sec = True
            cfunchead = ftr[6:]
    cfileobj = None
    if cfilename:
        #print("FILLING C FILE: ", cfilename)
        if cfilename in CCODE_GEN_DICT:
            cfileobj = CCODE_GEN_DICT[cfilename]
        else:
            cfileobj = CFile(cfilename)
            CCODE_GEN_DICT[cfilename] = cfileobj
    cfuncobj = None
    if is_fun_sec:
        #print("FILLING C FUNC: ", cfunchead)
        if cfunchead in cfileobj.funs:
            cfuncobj = cfileobj.funs[cfunchead]
        else:
            cfuncobj = CFunc(cfunchead)
            cfileobj.funs[cfunchead] = cfuncobj
    #if is_def_sec:
    #    print("FILLING C DEFNITION")
    #start exec
    # 有可能前面没有消耗任何内容
    opidx = max(opidx-1, -1)
    while opidx < opslen-1:
        opidx += 1
        op = ast.oplist[opidx]
        #嵌套的时候把现在的环境传过去
        if isinstance(op, BlockOp):
            __ccode_gen_static(stab, op, pre_env=env, pre_param=param)
        if isinstance(op, String):
            if 'pystring' in env or op.pystring:
                sval = op.value
                sval = sval.format_map(param)
                print("eval following code: ")
                print(sval)
                print("evaling -------->")
                exec(sval, EXEC_NS_G, EXEC_NS_L)
                print("<--------")
        if isinstance(op, String) and cfilename != None:
            if op.strong or op.pystring:
                continue
            opval = op.value
            opval = opval.format_map(param)
            if is_def_sec:
                cfileobj.cdef.append(opval)
            if is_fun_sec:
                cfuncobj.lins.append(opval)
    return


class CFunc():
    def __init__(self, head):
        self.head = head
        self.lins = []

    def __str__(self):
        ret = self.head + "{\n"
        for li in self.lins:
            ret += '    ' + li + '\n'
        ret += '}\n'
        return ret

    def __repr__(self):
        return str(self)


class CFile():
    def __init__(self, name):
        self.name = name
        self.funs = {}
        self.cdef = []
    
    def __str__(self):
        cdefs = '\n'.join(self.cdef)
        cfuns = '\n'.join(str(v) for v in self.funs.values())
        return cdefs + '\n\n' + cfuns

    def __repr__(self):
        return str(self)
